#include<atomic>
#include<codecvt>
#include <functional>
#include <thread>
#include"FileMonitorImp.h"

CFileMonitorImp::CFileMonitorImp()
{
}

CFileMonitorImp::CFileMonitorImp(const std::vector<std::wstring> &vecMonitorDir)
    :m_vecMonitorDir(vecMonitorDir)
{
}

CFileMonitorImp::~CFileMonitorImp()
{
}

bool CFileMonitorImp::Init()
{
    if (m_vecMonitorDir.empty())
    {
        return false;
    }
    auto fun = std::bind(&CFileMonitorImp::HandleMessage, this, std::placeholders::_1);
    bool bFlag = MSG_QUEUE::Instance().Initial(fun);

    for (auto &varDir : m_vecMonitorDir)
    {
        PDIRECTORY_INFO pdirInfo = new DIRECTORY_INFO;
        memset(pdirInfo, 0, sizeof(DIRECTORY_INFO));
        // Get a handle to the directory
        pdirInfo->hDir = CreateFile(varDir.c_str(),
            FILE_LIST_DIRECTORY,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL);
        if (pdirInfo->hDir == INVALID_HANDLE_VALUE)
        {
            wprintf(L"Unable to open directory %s. GLE=%d. Terminating...\n",
                varDir.c_str(), GetLastError());
            exit(0);
        }
        pdirInfo->wstrDirName = varDir;
        // Set up a key(directory info) for each directory
        m_hCompPort = CreateIoCompletionPort(pdirInfo->hDir,
            m_hCompPort, (DWORD)pdirInfo, 0);

        BOOL bFlag = ReadDirectoryChangesW(pdirInfo->hDir,            // HANDLE TO DIRECTORY
            pdirInfo->lpBuffer,                                       // Formatted buffer into which read results are returned.  This is a
            MAX_BUFFER, 
            TRUE, 
            FILE_NOTIFY_CHANGE_FILE_NAME |
            FILE_NOTIFY_CHANGE_DIR_NAME |
            FILE_NOTIFY_CHANGE_CREATION |
            FILE_NOTIFY_CHANGE_LAST_WRITE,            // What we are watching for
            &pdirInfo->dwBufLength,                                    // Number of bytes returned into second parameter
            &pdirInfo->Overlapped,                                          // OVERLAPPED structure that supplies data to be used during an asynchronous operation.  If this is NULL, ReadDirectoryChangesW does not return immediately.
            NULL);
        if (!bFlag)
        {
            printf("error code : %d\n", GetLastError());
        }
        m_vecDirInfo.push_back(pdirInfo);
    }
    return true;
}

void CFileMonitorImp::Start()
{
    MSG_QUEUE::Instance().Start();

    std::thread th(std::bind(&CFileMonitorImp::HandleDirectoryChange, this));
    m_thHandleMsg = std::move(th);
}

void CFileMonitorImp::HandleDirectoryChange()
{
    DWORD numBytes;
    LPDIRECTORY_INFO di;
    LPOVERLAPPED lpOverlapped;

    do
    {
        // Retrieve the directory info for this directory
        // through the completion key
        GetQueuedCompletionStatus(m_hCompPort,
            &numBytes,
            (LPDWORD)&di,      // This is the DIRECTORY_INFO structure that was passed in the call to CreateIoCompletionPort below.
            &lpOverlapped,
            INFINITE);

        if (di != nullptr && !di->wstrDirName.empty())
        {
            std::wstring temp = di->wstrDirName;
            //wchar_t wc = temp.back();
            std::wstring wstrDirName;
            //if (di->wstrDirName.back() == L'\\')
            if (temp.back() == L'\\')
            {
                wstrDirName = di->wstrDirName;
            }
            else
            {
                wstrDirName = di->wstrDirName + L'\\';
            }
            PFILE_NOTIFY_INFORMATION fni = (PFILE_NOTIFY_INFORMATION)di->lpBuffer;
            DWORD cbOffset = 0;
            do
            {
                cbOffset = fni->NextEntryOffset;
                std::wstring wstrFileName(fni->FileName, fni->FileNameLength / sizeof(WCHAR));
                std::wstring wstrFileFullPath = wstrDirName + wstrFileName;
                std::wstring_convert<std::codecvt<wchar_t, char, mbstate_t>> converter(
                    new std::codecvt<wchar_t, char, mbstate_t>("CHS"));
                std::string narrowStr = converter.to_bytes(wstrFileFullPath);
                DIR_CHANGE_MSG dirChangeMsg = { 0 };
                dirChangeMsg.dwAction = fni->Action;
                dirChangeMsg.strFilePath = narrowStr;
                MSG_QUEUE::Instance().PutTask(dirChangeMsg);

                fni = (PFILE_NOTIFY_INFORMATION)((LPBYTE)fni + cbOffset);

            } while (cbOffset);

            BOOL bFlag = ReadDirectoryChangesW(di->hDir, di->lpBuffer,
                MAX_BUFFER,
                TRUE,
                FILE_NOTIFY_CHANGE_FILE_NAME |
                FILE_NOTIFY_CHANGE_DIR_NAME |
                FILE_NOTIFY_CHANGE_CREATION |
                FILE_NOTIFY_CHANGE_LAST_WRITE,
                &di->dwBufLength,
                &di->Overlapped,
                NULL);
            if (!bFlag)
            {
                printf("error code : %d\n", GetLastError());
            }
        }
    } while (di);
}

bool CFileMonitorImp::HandleMessage(const DIR_CHANGE_MSG &msg)
{
    static int i = 0;
    printf("%d,\t%d,\t%s\n", ++i, msg.dwAction, msg.strFilePath.c_str());
    return true;
}

void CFileMonitorImp::UnInit()
{
    for (auto var : m_vecDirInfo)
    {
        delete var;
        var = nullptr;
    }
    //The user has quit - clean up
    PostQueuedCompletionStatus(m_hCompPort, 0, 0, NULL);
    CloseHandle(m_hCompPort);
    //Wait for the Directory thread to finish before exiting
    if (m_thHandleMsg.joinable())
    {
        m_thHandleMsg.join();
    }
}

